package com.lion.seaman.worker.flows;

import com.lion.seaman.common.LoadJs;
import com.lion.seaman.common.tools.StringTools;
import com.lion.seaman.common.type.JobFlowStatus;
import com.lion.seaman.common.model.TimeValue;
import com.lion.seaman.worker.flows.logic.IScriptLogic;
import org.apache.log4j.Logger;
import org.springframework.util.Assert;

import java.io.File;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class JobFlow implements Runnable {
    protected String flowId;
    protected boolean retryOnError = false;//当错误时进行重试
    protected int retryCount = 3;//可以重试次数
    protected int retry = 0;//执行重试的次数
    protected TimeValue retryInterval = TimeValue.create(5, TimeUnit.SECONDS);//重试间隔
    protected ConcurrentHashMap<String, Object> flowData = new ConcurrentHashMap<>();//数据传递
    protected JobFlowStatus jobFlowStatus = JobFlowStatus.REDAY;
    protected CountDownLatch countDownLatch;
    protected Semaphore semaphore;
    protected String scriptPath;
    protected IScriptLogic scriptLogic;
    private Logger log = Logger.getLogger(JobFlow.class);


    public JobFlow(String scriptPath) {
        initEngine(scriptPath);
    }

    public JobFlow(IScriptLogic scriptLogic, String flowId) {
        Assert.hasLength(flowId, "flowId must not be null");
        Assert.notNull(scriptLogic, "scriptLogic must not be null");
        flowData = new ConcurrentHashMap<>();
        this.flowId = flowId;
        this.scriptLogic = scriptLogic;
        flowData.put("flowId", flowId);
        init();
    }

    private void initEngine(String scriptPath) {
        try {
            System.out.println("get file:" + new File(scriptPath).getAbsoluteFile());
            this.scriptLogic = LoadJs.load(scriptPath, scriptPath);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("job flow init error," + e.getMessage(), e);
        }
    }

    public JobFlow(String flowId, String scriptPath) {
        this(flowId, scriptPath, false);
    }

    public JobFlow(String flowId, String scriptPath, boolean isRoot) {
        this(scriptPath);
        Assert.hasLength(flowId, "flowId must not be null");
        Assert.hasLength(flowId, "scriptPath must not be null");
        flowData.put("flowId", flowId);
        this.flowId = flowId;
        this.scriptPath = scriptPath;
        init();
    }

    public JobFlow(String flowId, String scriptPath, CountDownLatch countDownLatch, Semaphore semaphore) {
        this(flowId, scriptPath);
        this.countDownLatch = countDownLatch;
        this.semaphore = semaphore;
    }

    public void run() {
        execute();
        switch (this.jobFlowStatus) {
            case ERROR:
                error();
                break;
            case SUCCESS:
                success();
                break;
        }
        complete();
    }

    public void execute() {
        jobFlowStatus = JobFlowStatus.RUNNING;
        try {
            scriptLogic.execute(this);
            jobFlowStatus = JobFlowStatus.SUCCESS;
        } catch (Exception e) {
            e.printStackTrace();
            jobFlowStatus = JobFlowStatus.ERROR;
            log.error(StringTools.format("flow {0} status is ERROR,message:{1}", flowId, e.getMessage()), e);
        } finally {
            //如果flow状态为ERROR并且还有重试次数，则进行重试
            if (retryOnError && this.jobFlowStatus == JobFlowStatus.ERROR && retry < retryCount) {
                retry++;
                try {
                    Thread.sleep(retryInterval.toMillis());
                } catch (InterruptedException e) {
                }
                execute();//重试
            }
        }
    }

    public void init() {
        scriptLogic.init(flowData);
    }

    public void error() {
        jobFlowStatus = JobFlowStatus.ERROR;
        scriptLogic.error(this);
    }

    public void success() {
        jobFlowStatus = JobFlowStatus.SUCCESS;
        scriptLogic.success(this);
    }

    public void complete() {
        if (countDownLatch != null) {
            countDownLatch.countDown();
        }
        if (semaphore != null) {
            semaphore.release();
        }
        log.info(StringTools.format("flow {0} is out of,status:{1},retry:{2}", flowId, jobFlowStatus, retry));
    }

    protected JobFlow useData(Map<String, Object> data) {
        this.flowData.putAll(data);
        return this;
    }

    protected JobFlow setData(Map<String, Object> data) {
        this.flowData.clear();
        this.flowData.putAll(data);
        return this;
    }

    protected void putData(String key, Object value) {
        this.flowData.put(key, value);
    }

    protected void getData(String key) {
        this.flowData.get(key);
    }

    public String getFlowId() {
        return flowId;
    }

    public ConcurrentHashMap<String, Object> getFlowData() {
        return flowData;
    }

    public JobFlowStatus getJobFlowStatus() {
        return jobFlowStatus;
    }

    public void setJobFlowStatus(JobFlowStatus jobFlowStatus) {
        this.jobFlowStatus = jobFlowStatus;
    }

    public Logger getLog() {
        return log;
    }


    public void setFlowId(String flowId) {
        this.flowId = flowId;
    }

    public boolean isRetryOnError() {
        return retryOnError;
    }

    public void setRetryOnError(boolean retryOnError) {
        this.retryOnError = retryOnError;
    }

    public int getRetryCount() {
        return retryCount;
    }

    public void setRetryCount(int retryCount) {
        this.retryCount = retryCount;
    }

    public int getRetry() {
        return retry;
    }

    public void setRetry(int retry) {
        this.retry = retry;
    }

    public TimeValue getRetryInterval() {
        return retryInterval;
    }

    public void setRetryInterval(TimeValue retryInterval) {
        this.retryInterval = retryInterval;
    }

    public CountDownLatch getCountDownLatch() {
        return countDownLatch;
    }

    public String getScriptPath() {
        return scriptPath;
    }

    public void setScriptPath(String scriptPath) {
        this.scriptPath = scriptPath;
    }

    public IScriptLogic getScriptLogic() {
        return scriptLogic;
    }

    public void setScriptLogic(IScriptLogic scriptLogic) {
        this.scriptLogic = scriptLogic;
    }

    public void setLog(Logger log) {
        this.log = log;
    }

    public JobTreeItem toJobItem(String parentFlowId) {
        Assert.hasLength(parentFlowId, "parentFlowId must not be null");
        Assert.hasLength(this.flowId, "flowId must not be null");
        JobTreeItem item = new JobTreeItem();
        item.setFlowId(this.flowId);
        item.setJobFlow(this);
        item.setParentFlowId(parentFlowId);
        return item;
    }

}
